<template>
  <div
    class="relative"
    ref="containerTarget"
    :style="{
      height: containerHeight + 'px'
    }"
  >
    <!-- 因为列数不确定，所以需要根据列数计算每列的宽度，所以等待列宽计算完成，并且有了数据源之后进行渲染 -->
    <template v-if="columnWidth && data.length">
      <div
        v-for="(item, index) in data"
        :key="nodeKey ? item[nodeKey] : index"
        class="z-waterfall-item absolute duration-300"
        :style="{
          top: item._style?.top + 'px',
          left: item._style?.left + 'px',
          width: columnWidth + 'px'
        }"
      >
        <slot :item="item" :width="columnWidth" :index="index" />
      </div>
    </template>
    <div v-else class="text-center text-lg">加载中...</div>
  </div>
</template>

<script setup>
import { ref, computed, onMounted, watch, nextTick, onUnmounted } from 'vue'
import {
  getImgElements,
  generateImgUrls,
  waitImgLoad,
  getMinHeightColumn,
  getMinHeight,
  getMaxHeight
} from './utils'

const props = defineProps({
  // 数据源
  data: {
    type: Array,
    required: true
  },
  // 唯一标识
  nodeKey: {
    type: String,
    required: true
  },
  // 列数
  column: {
    type: Number,
    default: 2
  },
  // 列间距
  columnGap: {
    type: Number,
    default: 20
  },
  // 行间距
  rowGap: {
    type: Number,
    default: 20
  },
  // 图片预加载
  picturePreReading: {
    type: Boolean,
    default: true
  }
})
// 容器高度
const containerHeight = ref(0)
// 记录每列高度的容器
const columnHeightObj = ref({})
// 构建记录每列高度的容器
const useColumnHeightObj = () => {
  columnHeightObj.value = {}
  for (let i = 0; i < props.column; i++) {
    columnHeightObj.value[i] = 0
  }
}
// 容器实例
const containerTarget = ref(null)
// 容器宽度（不包含padding margin border）
const containerWidth = ref(0)
// 容器左边距
const containerLeft = ref(0)
/**
 * 计算容器宽度
 */
const useContainerWidth = () => {
  const { paddingLeft, paddingRight } = getComputedStyle(
    containerTarget.value,
    null
  )
  // 容器左边距
  containerLeft.value = parseFloat(paddingLeft)
  // 容器宽度
  containerWidth.value =
    containerTarget.value.offsetWidth -
    containerLeft.value -
    parseFloat(paddingRight)
}

// 列宽度 = （容器宽度  - 列间距） / 列数
const columnWidth = ref(0)
// 列间距合计
const columnGapTotal = computed(() => {
  return props.columnGap * (props.column - 1)
})
/**
 * 计算列宽度
 */
const useColumnWidth = () => {
  useContainerWidth()
  columnWidth.value =
    (containerWidth.value - columnGapTotal.value) / props.column
}

onMounted(() => {
  // 初始化
  useColumnWidth()
})

// 图片加载
let itemHeights = []
/**
 * 图片加载
 */
const usePicturePreReading = () => {
  itemHeights = []
  let itemElements = [...document.getElementsByClassName('z-waterfall-item')]
  const imgElements = getImgElements(itemElements)
  // 获取所有img标签的图片
  const allImgs = generateImgUrls(imgElements)
  // 等待加载图片
  waitImgLoad(allImgs).then(() => {
    // 加载完成，获取所有图片的高度
    itemElements.forEach((item) => {
      itemHeights.push(item.offsetHeight)
    })
    // 渲染位置
    useRenderPosition()
  })
}

/**
 * 不需要图片预加载
 */
const useItemHeight = () => {
  itemHeights = []
  let itemElements = [...document.getElementsByClassName('z-waterfall-item')]
  // 获取所有图片的高度
  itemElements.forEach((item) => {
    itemHeights.push(item.offsetHeight)
  })
  // 渲染位置
  useRenderPosition()
}

// 渲染位置
const useRenderPosition = () => {
  // 初始化
  props.data.forEach((item, index) => {
    if (item._style) return
    item._style = {}
    item._style.left = getNextItemLeft()
    item._style.top = getNextItemTop()
    // 指定列高增加
    increasingHeight(index)
  })
  // 计算容器高度
  containerHeight.value = getMaxHeight(columnHeightObj.value)
}

/**
 * 返回下一个item的left
 */
const getNextItemLeft = () => {
  return (
    getMinHeightColumn(columnHeightObj.value) *
      (columnWidth.value + props.columnGap) +
    containerLeft.value
  )
}

/**
 * 返回下一个item的top
 */
const getNextItemTop = () => {
  return getMinHeight(columnHeightObj.value)
}

/**
 * 指定列高度自增
 */
const increasingHeight = (columnIndex) => {
  const minHeightColumn = getMinHeightColumn(columnHeightObj.value)
  columnHeightObj.value[minHeightColumn] +=
    itemHeights[columnIndex] + props.rowGap
}

/**
 * 触发计算
 */
watch(
  () => props.data,
  (newVal) => {
    // 初始化获取数据时，构建记录每列高度的容器
    const resetColumnHeight = newVal.every((item) => !item._style)
    if (resetColumnHeight) {
      useColumnHeightObj()
    }
    nextTick(() => {
      if (props.picturePreReading) {
        usePicturePreReading()
      } else {
        useItemHeight()
      }
    })
  },
  {
    deep: true,
    immediate: true
  }
)

/**
 * 组件销毁
 */
onUnmounted(() => {
  props.data.forEach((item) => {
    delete item._style
  })
})

/**
 * 重置
 */
const reset = () => {
  setTimeout(() => {
    useColumnWidth()
    props.data.forEach((item) => {
      delete item._style
    })
  }, 200)
}

/**
 * 监听列数的变化
 */
watch(
  () => props.column,
  () => {
    if (props.picturePreReading) {
      columnWidth.value = 0
      nextTick(() => {
        reset()
      })
    } else {
      reset()
    }
  }
)
</script>

<style lang="scss" scoped></style>
